package tum0r.webengine.core

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import tum0r.webengine.config.ApplicationConfig
import tum0r.webengine.config.BaseConfig
import tum0r.webengine.enums.EConfigType
import tum0r.webengine.enums.EFileType
import tum0r.webengine.enums.ERequestMethod
import tum0r.webengine.extension.check
import tum0r.webengine.extension.errorLog
import tum0r.webengine.extension.infoLog
import tum0r.webengine.models.core.Action
import tum0r.webengine.models.core.EngineFile
import tum0r.webengine.models.core.HttpError
import tum0r.webengine.models.core.HttpRequest
import tum0r.webengine.models.mapping.ServerMapping
import tum0r.webengine.models.utils.CSV
import java.io.File
import java.io.FileInputStream
import java.io.OutputStream
import java.io.PrintStream
import java.lang.reflect.InvocationTargetException
import java.net.Socket
import java.nio.charset.StandardCharsets

/**
 * 说明: 请求处理
 *
 * 创建者: tum0r
 *
 * 时间: 2021/8/25 15:13
 *
 * @param[socket] 请求的客户端Socket
 */
class RequestHandler(private val socket: Socket) : Runnable {

    /**
     * 检查请求地址映射，如果第一遍找不到，根据映射信息查找静态资源，如找到更新映射列表
     *
     * @param[requestURL] 请求地址
     */
    private fun checkMapping(requestURL: String): ServerMapping? {
        var result: ServerMapping? = null
        // 获取请求的映射信息
        for (mapping in ApplicationConfig.SERVER_MAPPINGS) {
            if (mapping.path == requestURL) {
                result = mapping
                break
            }
        }
        // 获取不到则刷新一遍静态文件夹，最后还是获取不到则证明没有此映射
        if (result == null) {
            for (item in ApplicationConfig.STATIC_RESOURCES) {
                if (requestURL.startsWith(item.resourceMapping)) {
                    val filename = File.separator + requestURL.replaceFirst(item.resourceMapping, "").replace("/", File.separator)
                    val file = File(item.resourceDirectory.absolutePath + filename)
                    result = ServerMapping(requestURL, -1, ERequestMethod.MIXTURE, null, -1, file, null)
                    break
                }
            }
        }
        return result
    }

    /**
     * 错误处理
     */
    private fun error(action: Action<Any>, e: Exception) {
        val error = when (e) {
            is InvocationTargetException -> when (e.targetException) {
                is ExceptionInInitializerError -> {
                    e.targetException.printStackTrace()
                    HttpError(500, e.targetException.message ?: "接口请求错误")
                }
                else -> e.targetException as Exception
            }
            else -> e
        }
        if (error !is HttpError) error.errorLog()
        action.result = if (error is HttpError) error else HttpError(500, error.message ?: "未知错误")
    }

    /**
     * 解析请求并返回结果
     */
    override fun run() {
        if (socket.isClosed) return
        // 解析请求头
        val request = HttpRequest(socket)
        // 返回信息
        val action = Action<Any>()
        try {
            request.parse()
        } catch (e: Exception) {
            // 请求头为空，猜测是长时间没有通信尝试再次连接
            if (e is IndexOutOfBoundsException) {
                socket.close()
                return
            }
            error(action, e)
        }
        var handleAfterResponse = false
        // http解析没有错误执行
        if (action.result == null) {
            // 根据请求路径返回相关数据
            try {
                action.setRequest(request)
                action.remoteAddress = socket.inetAddress
                action.localAddress = socket.localAddress

                val requestURL = request.headers["RequestURL"]!!
                val mapping = checkMapping(requestURL)
                "未找到此请求路径 $requestURL".check(mapping == null, 404)
                // 过滤
                for (filter in ApplicationConfig.FILTERS) if (filter.equalsRequestURL(requestURL)) filter.message().check(!filter.filter(action), 403)
                // 处理请求
                when (mapping!!.index) {
                    // 请求网站图标
                    -2 -> {
                        val inputStream = if (mapping.icon!!.iconFile == null) this::class.java.classLoader.getResourceAsStream(mapping.icon.defaultIcon)
                        else FileInputStream(mapping.icon.iconFile!!)
                        action.result = inputStream.readAllBytes()
                    }
                    // 请求静态资源
                    -1 -> action.result = mapping.staticResource
                    // 请求接口
                    else -> {
                        // 打印访问信息
                        "${action.remoteAddress?.hostAddress} 访问 $requestURL".infoLog()
                        "禁止获取设备信息".check(requestURL.startsWith("/built_in/getDeviceInfo") && !BaseConfig.isState(EConfigType.CAN_GET_DEVICE_INFO), 403)
                        val parameterInfo = ApplicationConfig.SERVER_METHOD_PARAMETERS_MAPPING[mapping.functionIndex].getValues(request.body, action)
                        // 执行请求的方法
                        synchronized(mapping.function!!) {
                            mapping.function.invoke(ApplicationConfig.SERVER_OBJECTS[mapping.index], *parameterInfo)
                        }
                        // 设置需要处理信息
                        handleAfterResponse = true
                    }
                }
            } catch (e: Exception) {
                error(action, e)
            }
        }
        var outputStream: OutputStream? = null
        var printStream: PrintStream? = null
        try {
            var result = action.result
            if (socket.isClosed) return
            outputStream = socket.getOutputStream()
            // 根据结果类型改变响应头信息
            when (result) {
                is String -> action.addResponseHeader("Content-Length", result.toByteArray().size)
                is File -> {
                    val mime = BaseConfig.getMIME(result.extension)
                    if (mime[0] == EFileType.Text.name) action.addResponseHeader("Content-Type", "${BaseConfig.getMIME(result.extension)[1]}; charset=utf-8")
                    else action.addResponseHeader("Content-Type", BaseConfig.getMIME(result.extension)[1])
                    action.addResponseHeader("Content-Length", result.length())
                }
                is EngineFile -> {
                    action.addResponseHeader("Content-Type", result.getFileMIME())
                    action.addResponseHeader("Content-Disposition", "attachment; filename=${result.getFileDownloadName()}")
                    action.addResponseHeader("Content-Length", result.file.length())
                }
                is CSV -> {
                    action.addResponseHeader("Content-Type", BaseConfig.getMIME("csv")[1])
                    action.addResponseHeader("Content-Disposition", "attachment; filename=${result.getFileDownloadName()}")
                    result = result.getResultHexStringByteArray()
                    action.addResponseHeader("Content-Length", result.size)
                }
                is ByteArray -> {
                    action.addResponseHeader("Content-Type", "application/octet-stream")
                    action.addResponseHeader("Content-Length", result.size)
                }
                is HttpError -> {
                    val temp = JSONObject()
                    temp["code"] = result.errCode
                    temp["msg"] = result.errorMessage
                    result = temp.toString()
                    action.addResponseHeader("Content-Length", result.toByteArray().size)
                }
                else -> {
                    result = if (result != null) JSON.toJSONString(result) else "null"
                    action.addResponseHeader("Content-Length", result.toByteArray().size)
                }
            }
            // 打印响应头
            printStream = PrintStream(outputStream!!, true)
            printStream.print(action.getResponseHeadersString())
            // 根据结果类型返回响应内容
            when (result) {
                is String -> printStream.print(result)
                is File -> {
                    val mime = BaseConfig.getMIME(result.extension)
                    if (mime[0] == EFileType.Text.name) {
                        printStream.print(result.readText(StandardCharsets.UTF_8))
                    } else {
                        result.forEachBlock { ByteArray, _ ->
                            outputStream.write(ByteArray)
                        }
                    }
                }
                is EngineFile -> {
                    result.file.forEachBlock { ByteArray, _ ->
                        outputStream.write(ByteArray)
                    }
                }
                is ByteArray -> outputStream.write(result)
                else -> printStream.println(JSON.toJSONString(result))
            }
            outputStream.flush()
        } catch (e: Exception) {
            val message = e.message
            if (message != null && !message.equals("Broken pipe", true) && !message.equals("Protocol wrong type for socket", true))
                message.errorLog()
        } finally {
            // 关闭流
            printStream?.close()
            outputStream?.close()
            socket.close()

            // 后续处理
            if (ApplicationConfig.AFTER_RESPONSE.size != 0 && handleAfterResponse)
                for (afterResponse in ApplicationConfig.AFTER_RESPONSE)
                    if (!afterResponse.dispose()) afterResponse.errMessage.errorLog()
        }
    }
}